import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';

import { useChatStore } from '@/store/chat/store';

import { operationSelectors } from '../selectors';

describe('Operation Selectors', () => {
  beforeEach(() => {
    useChatStore.setState(useChatStore.getInitialState());
  });

  describe('getOperationsByType', () => {
    it('should return operations of specific type', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1' },
        });

        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1' },
        });

        result.current.startOperation({
          type: 'reasoning',
          context: { sessionId: 'session1' },
        });
      });

      const generateOps = operationSelectors.getOperationsByType('execAgentRuntime')(
        result.current,
      );
      const reasoningOps = operationSelectors.getOperationsByType('reasoning')(result.current);

      expect(generateOps).toHaveLength(2);
      expect(reasoningOps).toHaveLength(1);
    });
  });

  describe('getCurrentContextOperations', () => {
    it('should return operations for current active session/topic', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        // Set active session and topic
        useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });

        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1' },
        });

        result.current.startOperation({
          type: 'reasoning',
          context: { sessionId: 'session1', topicId: 'topic1' },
        });

        // Operation in different context
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session2', topicId: 'topic2' },
        });
      });

      const currentOps = operationSelectors.getCurrentContextOperations(result.current);

      expect(currentOps).toHaveLength(2);
      expect(currentOps.every((op) => op.context.sessionId === 'session1')).toBe(true);
      expect(currentOps.every((op) => op.context.topicId === 'topic1')).toBe(true);
    });
  });

  describe('hasAnyRunningOperation', () => {
    it('should return true if any operation is running', () => {
      const { result } = renderHook(() => useChatStore());

      expect(operationSelectors.hasAnyRunningOperation(result.current)).toBe(false);

      let opId: string;

      act(() => {
        opId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1' },
        }).operationId;
      });

      expect(operationSelectors.hasAnyRunningOperation(result.current)).toBe(true);

      act(() => {
        result.current.completeOperation(opId!);
      });

      expect(operationSelectors.hasAnyRunningOperation(result.current)).toBe(false);
    });
  });

  describe('hasRunningOperationType', () => {
    it('should return true if specific type is running', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1' },
        });
      });

      expect(operationSelectors.hasRunningOperationType('execAgentRuntime')(result.current)).toBe(
        true,
      );
      expect(operationSelectors.hasRunningOperationType('reasoning')(result.current)).toBe(false);
    });
  });

  describe('canSendMessage', () => {
    it('should return false when operations are running in current context', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });
      });

      expect(operationSelectors.canSendMessage(result.current)).toBe(true);

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1' },
        });
      });

      expect(operationSelectors.canSendMessage(result.current)).toBe(false);
    });
  });

  describe('canInterrupt', () => {
    it('should return true when operations can be cancelled', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });
      });

      expect(operationSelectors.canInterrupt(result.current)).toBe(false);

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1' },
        });
      });

      expect(operationSelectors.canInterrupt(result.current)).toBe(true);
    });
  });

  describe('getCurrentOperationLabel', () => {
    it('should return label of most recent running operation', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });

        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1' },
          label: 'Generating response...',
        });

        // Simulate some time passing
        setTimeout(() => {
          result.current.startOperation({
            type: 'reasoning',
            context: { sessionId: 'session1', topicId: 'topic1' },
            label: 'Thinking...',
          });
        }, 10);
      });

      // Should return the most recent operation's label
      const label = operationSelectors.getCurrentOperationLabel(result.current);
      expect(label).toBeTruthy();
    });
  });

  describe('isMessageProcessing', () => {
    it('should return true if message has running operations', () => {
      const { result } = renderHook(() => useChatStore());

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', messageId: 'msg1' },
        });
      });

      expect(operationSelectors.isMessageProcessing('msg1')(result.current)).toBe(true);
      expect(operationSelectors.isMessageProcessing('msg2')(result.current)).toBe(false);
    });
  });

  describe('isMessageCreating', () => {
    it('should return true for user message during sendMessage operation', () => {
      const { result } = renderHook(() => useChatStore());

      let opId: string;

      act(() => {
        opId = result.current.startOperation({
          type: 'sendMessage',
          context: { sessionId: 'session1', messageId: 'user_msg_1' },
        }).operationId;

        // Associate message with operation
        result.current.associateMessageWithOperation('user_msg_1', opId!);
      });

      expect(operationSelectors.isMessageCreating('user_msg_1')(result.current)).toBe(true);
      expect(operationSelectors.isMessageCreating('other_msg')(result.current)).toBe(false);
    });

    it('should return true for assistant message during createAssistantMessage operation', () => {
      const { result } = renderHook(() => useChatStore());

      let opId: string;

      act(() => {
        opId = result.current.startOperation({
          type: 'createAssistantMessage',
          context: { sessionId: 'session1', messageId: 'assistant_msg_1' },
        }).operationId;

        // Associate message with operation
        result.current.associateMessageWithOperation('assistant_msg_1', opId!);
      });

      expect(operationSelectors.isMessageCreating('assistant_msg_1')(result.current)).toBe(true);
      expect(operationSelectors.isMessageCreating('other_msg')(result.current)).toBe(false);
    });

    it('should return false when operation completes', () => {
      const { result } = renderHook(() => useChatStore());

      let opId: string;

      act(() => {
        opId = result.current.startOperation({
          type: 'sendMessage',
          context: { sessionId: 'session1', messageId: 'msg1' },
        }).operationId;

        result.current.associateMessageWithOperation('msg1', opId!);
      });

      expect(operationSelectors.isMessageCreating('msg1')(result.current)).toBe(true);

      act(() => {
        result.current.completeOperation(opId!);
      });

      expect(operationSelectors.isMessageCreating('msg1')(result.current)).toBe(false);
    });

    it('should return false for other operation types', () => {
      const { result } = renderHook(() => useChatStore());

      let opId: string;

      act(() => {
        // execAgentRuntime should not be considered as "creating"
        opId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', messageId: 'msg1' },
        }).operationId;

        result.current.associateMessageWithOperation('msg1', opId!);
      });

      expect(operationSelectors.isMessageCreating('msg1')(result.current)).toBe(false);
    });

    it('should only check sendMessage and createAssistantMessage operations', () => {
      const { result } = renderHook(() => useChatStore());

      let sendMsgOpId: string;
      let createAssistantOpId: string;
      let toolCallOpId: string;

      act(() => {
        // sendMessage - should be creating
        sendMsgOpId = result.current.startOperation({
          type: 'sendMessage',
          context: { sessionId: 'session1', messageId: 'user_msg' },
        }).operationId;
        result.current.associateMessageWithOperation('user_msg', sendMsgOpId!);

        // createAssistantMessage - should be creating
        createAssistantOpId = result.current.startOperation({
          type: 'createAssistantMessage',
          context: { sessionId: 'session1', messageId: 'assistant_msg' },
        }).operationId;
        result.current.associateMessageWithOperation('assistant_msg', createAssistantOpId!);

        // toolCalling - should NOT be creating
        toolCallOpId = result.current.startOperation({
          type: 'toolCalling',
          context: { sessionId: 'session1', messageId: 'tool_msg' },
        }).operationId;
        result.current.associateMessageWithOperation('tool_msg', toolCallOpId!);
      });

      expect(operationSelectors.isMessageCreating('user_msg')(result.current)).toBe(true);
      expect(operationSelectors.isMessageCreating('assistant_msg')(result.current)).toBe(true);
      expect(operationSelectors.isMessageCreating('tool_msg')(result.current)).toBe(false);
    });
  });

  describe('getOperationContextFromMessage', () => {
    it('should return operation context from message ID', () => {
      const { result } = renderHook(() => useChatStore());

      let opId: string;

      act(() => {
        opId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1', messageId: 'msg1' },
        }).operationId;

        result.current.associateMessageWithOperation('msg1', opId!);
      });

      const context = operationSelectors.getOperationContextFromMessage('msg1')(result.current);

      expect(context).toBeDefined();
      expect(context?.sessionId).toBe('session1');
      expect(context?.topicId).toBe('topic1');
      expect(context?.messageId).toBe('msg1');
    });
  });

  describe('backward compatibility selectors', () => {
    it('isAgentRuntimeRunning should work', () => {
      const { result } = renderHook(() => useChatStore());

      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);

      act(() => {
        result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1' },
        });
      });

      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);
    });

    it('isSendingMessage should work', () => {
      const { result } = renderHook(() => useChatStore());

      expect(operationSelectors.isSendingMessage(result.current)).toBe(false);

      act(() => {
        result.current.startOperation({
          type: 'sendMessage',
          context: { sessionId: 'session1' },
        });
      });

      expect(operationSelectors.isSendingMessage(result.current)).toBe(true);
    });

    it('isInRAGFlow should work', () => {
      const { result } = renderHook(() => useChatStore());

      expect(operationSelectors.isInRAGFlow(result.current)).toBe(false);

      act(() => {
        result.current.startOperation({
          type: 'rag',
          context: { sessionId: 'session1' },
        });
      });

      expect(operationSelectors.isInRAGFlow(result.current)).toBe(true);
    });

    it('isMainWindowAgentRuntimeRunning should only detect main window operations', () => {
      const { result } = renderHook(() => useChatStore());

      // Set active context
      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);

      // Start a main window operation (inThread: false)
      let mainOpId: string;
      act(() => {
        mainOpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: null },
          metadata: { inThread: false },
        }).operationId;
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);

      // Complete main window operation
      act(() => {
        result.current.completeOperation(mainOpId!);
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);
    });

    it('isMainWindowAgentRuntimeRunning should exclude thread operations', () => {
      const { result } = renderHook(() => useChatStore());

      // Set active context
      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
      });

      // Start a thread operation (inThread: true)
      let threadOpId: string;
      act(() => {
        threadOpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: null, threadId: 'thread1' },
          metadata: { inThread: true },
        }).operationId;
      });

      // Thread operation should not affect main window state
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      // But should be detected by general isAgentRuntimeRunning
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);

      // Complete thread operation
      act(() => {
        result.current.completeOperation(threadOpId!);
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);
    });

    it('isMainWindowAgentRuntimeRunning should distinguish between main and thread operations', () => {
      const { result } = renderHook(() => useChatStore());

      // Set active context
      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
      });

      let mainOpId: string;
      let threadOpId: string;

      // Start both main window and thread operations
      act(() => {
        mainOpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: null },
          metadata: { inThread: false },
        }).operationId;

        threadOpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: null, threadId: 'thread1' },
          metadata: { inThread: true },
        }).operationId;
      });

      // Both selectors should detect their respective operations
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);

      // Complete main window operation, thread operation still running
      act(() => {
        result.current.completeOperation(mainOpId!);
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);

      // Complete thread operation
      act(() => {
        result.current.completeOperation(threadOpId!);
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);
    });

    it('isMainWindowAgentRuntimeRunning should exclude aborting operations', () => {
      const { result } = renderHook(() => useChatStore());

      // Set active context
      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
      });

      let opId: string;
      act(() => {
        opId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: null },
          metadata: { inThread: false },
        }).operationId;
      });

      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);

      // Mark as aborting
      act(() => {
        result.current.updateOperationMetadata(opId!, { isAborting: true });
      });

      // Should exclude aborting operations
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
      expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);
    });

    it('isMainWindowAgentRuntimeRunning should only detect operations in current active topic', () => {
      const { result } = renderHook(() => useChatStore());

      // Set active session and topic
      act(() => {
        useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });
      });

      let topic1OpId: string;
      let topic2OpId: string;

      // Start operation in topic1 (current active topic)
      act(() => {
        topic1OpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic1' },
          metadata: { inThread: false },
        }).operationId;
      });

      // Should detect operation in current topic
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);

      // Start operation in topic2 (different topic)
      act(() => {
        topic2OpId = result.current.startOperation({
          type: 'execAgentRuntime',
          context: { sessionId: 'session1', topicId: 'topic2' },
          metadata: { inThread: false },
        }).operationId;
      });

      // Should still only detect topic1 operation (current active topic)
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);

      // Switch to topic2
      act(() => {
        useChatStore.setState({ activeTopicId: 'topic2' });
      });

      // Should now detect topic2 operation
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);

      // Complete topic2 operation
      act(() => {
        result.current.completeOperation(topic2OpId!);
      });

      // Should not detect any operation in topic2 now
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);

      // Switch back to topic1
      act(() => {
        useChatStore.setState({ activeTopicId: 'topic1' });
      });

      // Should detect topic1 operation again
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);

      // Complete topic1 operation
      act(() => {
        result.current.completeOperation(topic1OpId!);
      });

      // Should not detect any operation now
      expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
    });
  });
});
